package cn.zhuoluodada.opensource.smartdb;

import cn.zhuoluodada.opensource.smartdb.database.SupportDatabaseType;
import cn.zhuoluodada.opensource.smartdb.datasource.*;
import cn.zhuoluodada.opensource.smartdb.exception.SmartDbException;
import cn.zhuoluodada.opensource.smartdb.ext.SmartDbExtEnum;
import cn.zhuoluodada.opensource.smartdb.ext.spring.SmartDbDataSource4Spring;
import cn.zhuoluodada.opensource.smartdb.filter.SmartDbFilter;
import cn.zhuoluodada.opensource.smartdb.filter.SmartDbFilterChainImpl;
import cn.zhuoluodada.opensource.smartdb.mapping.rowconvertor.CaseFormatRowConverter;
import cn.zhuoluodada.opensource.smartdb.sqlbuilder.convertor.ColumnNameConverter;
import cn.zhuoluodada.opensource.smartdb.sqlbuilder.convertor.DefaultColumnNameConverter;
import cn.zhuoluodada.opensource.smartdb.sqlbuilder.convertor.TableNameConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.List;

/**
 * 构造SmartDb的工厂
 *
 * @author zhuoluodada@qq.com
 */
public class SmartDbFactory {

    protected static Logger LOG = LoggerFactory.getLogger(AbstractSmartDb.class);

    public static SmartDb build(SmartDbBuilder builder) {
        //validate
        validate(builder);
        //create proxy
        SmartDbProxy proxy = proxy(builder);

        Class smartDbWrapperClass = builder.getSupportDatabaseType().getSmartDbWrapperClass();
        Constructor constructor = null;
        try {
            constructor = smartDbWrapperClass.getConstructor(SmartDb.class);
            return (SmartDb) constructor.newInstance(proxy.getInstance());
        } catch (Exception e) {
            throw new SmartDbException(e);
        }
    }

    private static void validate(SmartDbBuilder builder) {
        if (builder.getSupportDatabaseType() == null) {
            throw new IllegalArgumentException("SupportDatabaseType cannot be null");
        }

        boolean isMasterDatasourceIsNull = false;
        if (builder.getMasterDataSource() == null) {
            isMasterDatasourceIsNull = true;
        }

        boolean isConnectInfoIsNull = false;
        if (builder.getUrl() == null && builder.getPassword() == null && builder.getUsername() == null && builder.getDriverClassName() == null) {
            isConnectInfoIsNull = true;
        }

        if (isConnectInfoIsNull && isMasterDatasourceIsNull) {
            throw new IllegalArgumentException("MasterDataSource cannot be null or connect info cannot be null");
        }

        if (builder.getSlaveDataSource() != null) {
            for (DataSource dataSource : builder.getSlaveDataSource()) {
                if (dataSource == null) {
                    throw new IllegalArgumentException("SlaveDataSource cannot be null");
                }
            }
        }

        ColumnNameConverter columnNameConverter = builder.getColumnNameConverter();
        if (columnNameConverter == null) {
            columnNameConverter = new DefaultColumnNameConverter();
            builder.setColumnNameConverter(columnNameConverter);
        }

        TableNameConverter tableNameConverter = builder.getTableNameConverter();
        if (tableNameConverter == null) {
            tableNameConverter = new TableNameConverter() {
                @Override
                public String classToTableName(Class<?> cls) {
                    return cls.getSimpleName();
                }
            };
            builder.setTableNameConverter(tableNameConverter);
        }

        Class smartDbImplClass = builder.getSupportDatabaseType().getSmartDbImplClass();
        if (smartDbImplClass == null) {
            throw new IllegalArgumentException("SmartDbImplClass cannot be null");
        }

        boolean assignableFrom = AbstractSmartDb.class.isAssignableFrom(smartDbImplClass);
        if (!assignableFrom) {
            throw new IllegalArgumentException("SmartDbImplClass must extend AbstractSmartDb ");
        }
    }

    private static SmartDbProxy proxy(SmartDbBuilder builder) {
        SupportDatabaseType supportDatabaseType = builder.getSupportDatabaseType();

        //connect pool
        DataSource masterDataSource = builder.getMasterDataSource();
        if (masterDataSource == null) {
            masterDataSource = new InnerDataSource(builder);
        }

        SmartDbDataSource master = null;
        SmartDbExtEnum extEnum = builder.getSmartDbExtEnum();
        if (extEnum == null) {
            master = new SmartDbDataSourceImpl(masterDataSource);
        } else if (extEnum == SmartDbExtEnum.SPRING4) {
            master = new SmartDbDataSource4Spring(masterDataSource);
        } else if (extEnum == SmartDbExtEnum.SPRING5) {
            master = new SmartDbDataSource4Spring(masterDataSource);
        }else {
            master = new SmartDbDataSourceImpl(masterDataSource);
        }

        //构建 数据源 group
        DataSource[] slaveDataSource = builder.getSlaveDataSource();
        SmartDbDataSourceGroup dataSourceGroup = null;
        if (slaveDataSource == null) {
            dataSourceGroup = new SingleDataSourceGroup(master);
        } else {
            SmartDbDataSource[] slave = new SmartDbDataSource[slaveDataSource.length];
            for (int i = 0; i < slaveDataSource.length; i++) {
                slave[i] = new SmartDbDataSourceImpl(slaveDataSource[i]);
            }
            dataSourceGroup = new MasterSlaveDataSourceGroup(master, slave);
        }

        //使用用户自定义的数据源group
        if (builder.getSmartDbDataSourceGroup() != null) {
            dataSourceGroup = builder.getSmartDbDataSourceGroup();
        }

        //create smartdb impl
        Class smartDbImplClass = supportDatabaseType.getSmartDbImplClass();
        try {
            AbstractSmartDb db = (AbstractSmartDb) smartDbImplClass.newInstance();
            db.setDataSourceGroup(dataSourceGroup);
            db.setShowSql(builder.isShowSql());
            db.setDatabaseType(supportDatabaseType);
            db.setColumnNameConverter(builder.getColumnNameConverter());
            db.setTableNameConverter(builder.getTableNameConverter());
            if (builder.getRowConverter() == null) {
                db.setRowConverter(new CaseFormatRowConverter(builder.getColumnNameConverter()));
            } else {
                db.setRowConverter(builder.getRowConverter());
            }

            db.init();

            List<SmartDbFilter> filters = builder.getFilters();
            for (SmartDbFilter filter : filters) {
                LOG.debug("add filter : {}", filter.getClass().getName());
                SmartDbFilterChainImpl.addFilter(filter);
            }

            return new SmartDbProxy(db);

        } catch (Exception e) {
            throw new SmartDbException(e);
        }
    }

    private static class InnerDataSource implements DataSource {

        private String url;
        private String driverClassName;
        private String username;
        private String password;

        public InnerDataSource(SmartDbBuilder smartDbBuilder) {
            this.url = smartDbBuilder.getUrl();
            this.driverClassName = smartDbBuilder.getDriverClassName();
            this.username = smartDbBuilder.getUsername();
            this.password = smartDbBuilder.getPassword();
        }

        @Override
        public Connection getConnection() throws SQLException {
            try {
                Class.forName(this.driverClassName);
            } catch (ClassNotFoundException e) {
                throw new SmartDbException(e);
            }
            return DriverManager.getConnection(this.url, this.username, this.password);
        }

        @Override
        public Connection getConnection(String username, String password) throws SQLException {
            try {
                Class.forName(this.driverClassName);
            } catch (ClassNotFoundException e) {
                throw new SmartDbException(e);
            }
            return DriverManager.getConnection(this.url, username, password);
        }

        @Override
        public <T> T unwrap(Class<T> iface) throws SQLException {
            return null;
        }

        @Override
        public boolean isWrapperFor(Class<?> iface) throws SQLException {
            return false;
        }

        @Override
        public PrintWriter getLogWriter() throws SQLException {
            return null;
        }

        @Override
        public void setLogWriter(PrintWriter out) throws SQLException {

        }

        @Override
        public void setLoginTimeout(int seconds) throws SQLException {

        }

        @Override
        public int getLoginTimeout() throws SQLException {
            return 0;
        }

        public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException {
            return null;
        }
    }


}
